﻿//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Created By: Peter Riad (peter.riad@itworx.com)
// Creation Date : 1/1/2012
// Description : a custom XML navigation rpvider to be used for SharePoint portals where it is 
//               required to have a unified navigation bar across different site collections.
/////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace SPS2010UnifiedNavProvider
{
    class XMLNavigationProvider : XmlSiteMapProvider
    {
        #region Overriden Methods
        public override SiteMapNode CurrentNode
        {
            get
            {
                // first let the base resolve the current node
                SiteMapNode currentNode = base.CurrentNode;
                if (currentNode == null)
                {
                    string currentWebUrl = SPContext.Current.Web.ServerRelativeUrl;
                    currentNode = FindSiteMapNode(currentWebUrl);
                    if (currentNode == null)
                    {
                        //try to get site parent
                        currentNode = FindSiteMapNode(currentWebUrl.Substring(0, currentWebUrl.LastIndexOf('/')));
                    }
                }
                //need to highlight first level nodes only, exculde nodes that is not linkable(URL is empty)
                if (currentNode != null && currentNode.ParentNode != null && currentNode.ParentNode != RootNode && !string.IsNullOrEmpty(currentNode.ParentNode.Url))
                {
                    currentNode = currentNode.ParentNode;
                }
                return currentNode;

            }
        }
        public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
        {
            //NoTrimming                used for root site urls as no secrity check is required.
            //ListSubSites              user to list sub sites dinamically according to current login user permissions
            //ADGroupTrimming           used for links that should appear only to specific user groups (ex. Staff Links)

            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            //check if the node children already on Session or not, use them in case they are there.
            if (HttpContext.Current.Session != null && HttpContext.Current.Session["NavigationNode_" + node.Key] != null)
            {
                return SiteMapNodeCollection.ReadOnly((SiteMapNodeCollection)HttpContext.Current.Session["NavigationNode_" + node.Key]);
            }
            else
            {
                SiteMapNodeCollection subNodes = base.GetChildNodes(node);
                HttpContext current = HttpContext.Current;
                SiteMapNodeCollection newNodesColl = new SiteMapNodeCollection(subNodes.Count);
                foreach (SiteMapNode subNode in subNodes)
                {
                    if (subNode["ADGroupTrimming"] != null)
                    {
                        //permission is required based on AD group
                        string activeDirectoryGroups = subNode["ADGroupTrimming"].ToString();
                        string[] activeDirectoryGroupsColl = activeDirectoryGroups.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string groupName in activeDirectoryGroupsColl)
                        {
                            if (CheckUserIsInRole(groupName))
                            {
                                SiteMapNode newNode = subNode.Clone();
                                newNode.Title = subNode.Title;
                                newNodesColl.Add(newNode);
                                break;
                            }
                        }
                    }
                    else if (subNode["ListSubSites"] != null && subNode["ListSubSites"].ToString().ToLower() == "true")
                    {
                        SiteMapNode newNode = subNode.Clone();
                        newNode.Title = subNode.Title;
                        if (!string.IsNullOrEmpty(subNode.Url))
                        {
                            newNode.Url = GetFullURL(subNode.Url);
                            if (DoesUserHavePermission(newNode.Url))
                            {
                                //get child nodes for user
                                SiteMapNodeCollection childNodes = GetSiteChildren(node, newNode.Url);
                                if (childNodes != null && childNodes.Count > 0)
                                {
                                    newNode.ChildNodes = childNodes;
                                }
                                newNodesColl.Add(newNode);
                            }

                        }
                    }
                    else if (subNode["NoTrimming"] != null && subNode["NoTrimming"].ToString().ToLower() == "true")
                    {
                        SiteMapNode newNode = subNode.Clone();
                        newNode.Title = subNode.Title;
                        newNodesColl.Add(newNode);
                    }
                    else
                    {
                        //handle having nodes with no URL (ex. logical grouping root nodes)
                        if (string.IsNullOrEmpty(subNode.Url))
                        {
                            newNodesColl.Add(subNode);
                        }
                        else
                        {
                            //Check user permission on link based on SPS permission
                            SiteMapNode newNode = subNode.Clone();
                            newNode.Url = GetFullURL(subNode.Url);
                            if (DoesUserHavePermission(newNode.Url))
                                newNodesColl.Add(newNode);
                        }
                    }
                }
                if (HttpContext.Current.Session != null)
                    HttpContext.Current.Session["NavigationNode_" + node.Key] = newNodesColl;
                return SiteMapNodeCollection.ReadOnly(newNodesColl);
            }

        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Check if the user got access to a specific SharePoint site.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private bool DoesUserHavePermission(string url)
        {
            bool initialValue = SPSecurity.CatchAccessDeniedException;
            try
            {
                SPSecurity.CatchAccessDeniedException = false;
                using (SPSite site = new SPSite(url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        if (web.DoesUserHavePermissions(SPBasePermissions.ViewPages))
                        {
                            string rootFolder = web.RootFolder.Url;

                            Uri originalUrl = new Uri(url);
                            //AbsolutePath gets the server relative Url without the quesry strings (if any)
                            string urlWithNoPage = originalUrl.AbsolutePath;
                            if (urlWithNoPage.ToLower().EndsWith(".aspx"))
                            {
                                urlWithNoPage = urlWithNoPage.Substring(0, urlWithNoPage.LastIndexOf('/'));
                            }
                            if (string.Compare(urlWithNoPage, web.ServerRelativeUrl, true) == 0)
                            {
                                return true;
                            }
                            else
                            {
                                //the opened web is different from the requested Url
                                return false;
                            }
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }
            catch
            {
                //have no access
                return false;
            }
            finally
            {
                SPSecurity.CatchAccessDeniedException = initialValue;
            }
        }

        /// <summary>
        /// Get the child nodes of the current node.
        /// </summary>
        /// <param name="currentNode"></param>
        /// <param name="url"></param>
        /// <returns></returns>
        private SiteMapNodeCollection GetSiteChildren(SiteMapNode currentNode, string url)
        {
            SiteMapNodeCollection childNodes = new SiteMapNodeCollection();
            bool initialValue = SPSecurity.CatchAccessDeniedException;
            try
            {
                SPSecurity.CatchAccessDeniedException = false;
                using (SPSite site = new SPSite(url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        SPWebCollection webs = web.GetSubwebsForCurrentUser();
                        if (webs.Count > 0)
                        {
                            foreach (SPWeb subWeb in webs)
                            {
                                childNodes.Add(new SiteMapNode(this, currentNode.Key + subWeb.ID.ToString(), subWeb.Url, subWeb.Title, ""));
                                subWeb.Dispose();
                            }
                        }
                    }
                }
                return childNodes;
            }
            catch (Exception ex)
            {
                //have no access
                return childNodes;
            }
            finally
            {
                SPSecurity.CatchAccessDeniedException = initialValue;
            }
        }

        /// <summary>
        /// Get the full Url from a server relative Url.
        /// </summary>
        /// <param name="relativePath"></param>
        /// <returns></returns>
        private string GetFullURL(string relativePath)
        {
            if (string.IsNullOrEmpty(relativePath))
            {
                return SPContext.Current.Site.Url;
            }
            else if (relativePath.ToLower().StartsWith("http"))
            {
                return relativePath;
            }
            else
            {
                if (relativePath.StartsWith("~"))
                    relativePath = relativePath.Substring(1);
                return GetFullURL(relativePath, SPContext.Current.Site);
            }
        }

        /// <summary>
        /// check if user is memebr of AD group.
        /// </summary>
        /// <param name="userADGroupName"></param>
        /// <returns></returns>
        private bool CheckUserIsInRole(string userADGroupName)
        {
            try
            {
                bool isInRole = false;
                isInRole = HttpContext.Current.User.IsInRole(userADGroupName);
                return isInRole;
            }
            catch (Exception ex)
            {
                LogException(ex);
                throw;
            }

        }

        /// <summary>
        /// Gets the full Url from a server relative Url
        /// </summary>
        /// <param name="relativeUrl">The relative Url.</param>
        /// <param name="siteObj">The site object to use.</param>
        /// <returns></returns>
        private static string GetFullURL(string relativeUrl, SPSite siteObj)
        {
            try
            {

                if (string.IsNullOrEmpty(relativeUrl) || SPUrlUtility.IsUrlRelative(relativeUrl))
                {
                    return SPUtility.GetFullUrl(siteObj, relativeUrl);
                }
                return relativeUrl;
            }
            catch (Exception ex)
            {
                LogException(ex);
                return relativeUrl;
            }

        }

        /// <summary>
        /// Log exception to the event log
        /// </summary>
        /// <param name="ex"></param>
        private static void LogException(Exception ex)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}
